#pragma once

typedef unsigned int uint32;

class ShaderSource {
public:

	const char* getSource();
private:
	char* m_source;

	uint32 m_length;
};

class VertexShader {
public:
	VertexShader() : m_shader(0) {}

	VertexShader& operator=(const VertexShader& other);

	void compile(const char* source);

	operator int() const { return m_shader; }

private:
	int m_shader;
};

class FragmentShader {
public:
	FragmentShader() : m_shader(0) {}

	FragmentShader& operator=(const FragmentShader& other);

	void compile(const char* source);

	operator int() const { return m_shader;  }

private:
	int m_shader;
};

class Shader {
public:
	struct attribute {
		char* name;
		uint32 id;
	};

	Shader() : m_program(0) { }

	void setVertexShader(const VertexShader& vertex_shader) {
		m_vertex_shader = vertex_shader;
	}

	void setFragmentShader(const FragmentShader& fragment_shader) {
		m_fragment_shader = fragment_shader;
	}

	template<uint32 N> void compile(attribute(&attributes)[N]) {
		return compile(N, attributes);
	}

	void compile(uint32 num_attributes, attribute* attributes);

	void bind();

	GLint getUniformLocation(const char* var);

	void setSampler(GLint var, uint32 unit);
	inline void setSampler(const char* var, uint32 unit) { setSampler(getUniformLocation(var), unit); }

	void setSamplers(GLint var, uint32 count, const uint32* units);
	template<uint32 N> inline void setSamplers(const char* var, uint32(&units)[N]) { setSamplers(getUniformLocation(var), N, &units[0]); }

	void setFloat(GLint var, GLfloat v);
	inline void setFloat(const char* var, GLfloat v) { setFloat(getUniformLocation(var), v); }

	void setInteger(GLint var, GLint v);
	inline void setInteger(const char* var, GLint v) { setInteger(getUniformLocation(var), v); }

	void setUInteger(GLint var, GLuint v);
	inline void setUInteger(const char* var, GLuint v) { setUInteger(getUniformLocation(var), v); }

	void setUIntegerArray(GLint var, GLuint* v, uint32 N);
	template<uint32 N> void setUIntegerArray(const char* var, GLuint(&v)[N]) { setUIntegerArray(getUniformLocation(var), &v[0], N); }

	void setVector2i(GLint var, int x, int y);
	inline void setVector2i(const char* var, int x, int y) { setVector2i(getUniformLocation(var), x, y); }

	void setVector2f(GLint var, GLfloat x, GLfloat y);
	inline void setVector2f(const char* var, GLfloat x, GLfloat y) { setVector2f(getUniformLocation(var), x, y); }

	void setVector2f(GLint var, Vector2f v) { setVector2f(var, v[0], v[1]); }
	inline void setVector2f(const char* var, Vector2f v) { setVector2f(getUniformLocation(var), v); }

	void setVector3f(GLint var, GLfloat x, GLfloat y, GLfloat z);
	inline void setVector3f(const char* var, GLfloat x, GLfloat y, GLfloat z) { setVector3f(getUniformLocation(var), x, y, z); }

	void setVector3f(GLint var, Vector3f v) { setVector3f(var, v[0], v[1], v[2]); }
	inline void setVector3f(const char* var, Vector3f v) { setVector3f(getUniformLocation(var), v); }

	void setVector4i(GLint var, int x, int y, int z, int w);
	inline void setVector4i(const char* var, int x, int y, int z, int w) { setVector4i(getUniformLocation(var), x, y, z, w); }

	void setVector4f(GLint var, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
	inline void setVector4f(const char* var, GLfloat x, GLfloat y, GLfloat z, GLfloat w) { setVector4f(getUniformLocation(var), x, y, z, w); }
	//inline void setVector4f(const char* var, double x, double y, double z, double w) { setVector4f(getUniformLocation(var), (GLfloat) x, (GLfloat) y, (GLfloat) z, (GLfloat) w); }

	void setVector4f(GLint var, Vector4d v);
	inline void setVector4f(const char* var, Vector4d v) { setVector4f(getUniformLocation(var), v); }

	void setVector4f(GLint var, Vector4f v);
	inline void setVector4f(const char* var, Vector4f v) { setVector4f(getUniformLocation(var), v); }

	void setMatrix2f(GLint var, const GLfloat dat[]);
	inline void setMatrix2f(const char* var, const GLfloat dat[]) { setMatrix2f(getUniformLocation(var), dat); }

	void setMatrix2f(GLint var, const Matrix2f m);
	inline void setMatrix2f(const char* var, const Matrix2f m) { setMatrix2f(getUniformLocation(var), m); }

	void setMatrix3f(GLint var, const GLfloat dat[]);
	inline void setMatrix3f(const char* var, const GLfloat dat[]) { setMatrix3f(getUniformLocation(var), dat); }

	void setMatrix3f(GLint var, const Matrix3f m);
	inline void setMatrix3f(const char* var, const Matrix3f m) { setMatrix3f(getUniformLocation(var), m); }

	void setMatrix4f(GLint var, Matrix4f m);
	inline void setMatrix4f(const char* var, Matrix4f m) { setMatrix4f(getUniformLocation(var), m); }
private:

	int m_program;

	uint32 m_num_attributes;
	attribute* m_attributes;

	VertexShader m_vertex_shader;
	FragmentShader m_fragment_shader;
};